﻿/* Copyright © 2012 Gargol, Gargol at gbot dot org
 
This file is part of ADCCore.

ADCCore is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

ADCCore is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ADCCore.  If not, see <http://www.gnu.org/licenses/>. */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using ADCCore.Implementation;
using System.ComponentModel;
using System.IO;

namespace ADCCore.Classes
{
    public partial class ClientToClient
    {
        public delegate void DcFileTransferHandler(User user,String Filename);

        static readonly int bufferSize = 655350;
        static readonly int readInterval = 20;
        readonly byte[] readBuffer = new byte[bufferSize];
        private System.Windows.Forms.Timer readTimer;
        bool ssl = false;
        bool SentCINF = false;
        bool FileTransfer = false;

        Encoding encoding = Encoding.GetEncoding(1252);
        TcpClient client;

        NetworkStream stream;
        FileStream ms;
        SslStream sslstream;

        StringBuilder readQueue;
        //String fileContent = String.Empty;
        private User _user;
        private String _token = String.Empty;
        /// <summary>
        /// The DcClient used to User is connected to.
        /// </summary>
        public DcClient Sender = new DcClient();
        
        /// <summary>
        /// Token to identify ClientToClient Connection.
        /// </summary>
        public String token { set { _token = value; } }
        private FileDownload file = new FileDownload();

        /// <summary>
        /// Start a connection to the User.
        /// </summary>
        /// <param name="user">User to connect to.</param>
        /// <param name="port">The port to connect to.</param>
        public void Connect(User user, int port,bool usessl)
        {
            ssl = usessl;
            _user = user;
            client = new TcpClient(user.IP.ToString(), port);
            file = _user.DownloadList[0];
            if (ssl)
            {
                ServicePointManager.ServerCertificateValidationCallback = delegate(object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; };
                sslstream = new SslStream(client.GetStream(), true, new RemoteCertificateValidationCallback(ADCCore.Implementation.DcConnection.ValidateServerCertificate), new LocalCertificateSelectionCallback(ADCCore.Implementation.DcConnection.ValidateClientCertificate));
                //X509Certificate2 cert = new X509Certificate2(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\GBot\\settings\\client.crt");
                X509Certificate2Collection xc = new X509Certificate2Collection();
                //sslstream.AuthenticateAsClient(String.Empty, xc, System.Security.Authentication.SslProtocols.Default, false);
                sslstream.AuthenticateAsClient(user.IP.ToString(), xc, System.Security.Authentication.SslProtocols.Default, false);
            }
            else
            {
                stream = client.GetStream();
            }

            if (!Directory.Exists(file.DownloadPath))
            {
                System.Security.AccessControl.DirectorySecurity DirSec = new System.Security.AccessControl.DirectorySecurity();
                System.Security.AccessControl.FileSystemAccessRule FilAccess = new System.Security.AccessControl.FileSystemAccessRule(System.Security.Principal.WindowsIdentity.GetCurrent().Name, System.Security.AccessControl.FileSystemRights.Modify, System.Security.AccessControl.AccessControlType.Allow);
                DirSec.SetAccessRule(FilAccess);
                Directory.CreateDirectory(file.DownloadPath, DirSec);
            }
            //dloadfilename = user.Username + "_Newlist.xml.bz2";

            readTimer = new System.Windows.Forms.Timer();
            readTimer.Interval = readInterval;
            readTimer.Tick += readTimer_Tick;
            readTimer.Tag = "readTimer";
            readTimer.Start();
            SendRawMessage("CSUP ADBAS0 ADBASE ADTIGR ADBZIP ADZLIG ADADCS\n");
            //WriteDebug("Sending CSUP");
        }
        void Read(string frame)
        {
            if (FileTransfer)
            {
                //WriteDebug("FileTransfer True, sending to stream");
                WriteStream(encoding.GetBytes(GetReadQueue() + frame));

                //fileContent = "";
                
                return;
            }
            string messageBundle = null;
            if (frame.EndsWith("\n"))
            {
                //message bundle is intact
                messageBundle = GetReadQueue() + frame;
                //WriteDebug("frame="+frame);
                //WriteDebug("messagebundle="+messageBundle);
            }
            else
            {
                int index = frame.LastIndexOf('\n');
                if (index == -1)
                {
                    //whole frame is part of message bundle
                    AddToReadQueue(frame);
                }
                else
                {
                    //part of frame is a message bundle
                    messageBundle = GetReadQueue() + frame.Substring(0, index + 1);
                    //while the rest must be queued
                    AddToReadQueue(frame.Substring(index + 1));
                }
            }
            if (messageBundle != null)
            {
                // split the bundle into seperate messages..
                string[] messages = messageBundle.Split("\n".ToCharArray(),StringSplitOptions.RemoveEmptyEntries);
                foreach (string message in messages)
                {
                    // but only handle messages that actually carries any information 
                    //WriteDebug(message);
                    if (message.Length > 1)
                    {
                        if (message.StartsWith("CSND "))
                        {
                            //WriteDebug("Recived CSND");
                            Console.WriteLine(message);
                            string[] mess = message.Split(" ".ToCharArray());
                            file.Size = Convert.ToInt32(mess[4]);
                            
                            String fileContent = messageBundle.Replace(message+"\n","");
                            //WriteDebug("FileTransfer True, Sending to:" + file.DownloadPath + "\\" + file.FileName);
                            ms = new FileStream(file.DownloadPath + "\\" +file.FileName, FileMode.OpenOrCreate);
                            WriteStream(encoding.GetBytes(fileContent));
                            
                            FileTransfer = true;
                            return;
                        }
                        // and trigger event for each message without the trailing pipe
                        //OnMessage(client, message.Substring(0, message.Length));
                        Console.WriteLine("CtC Recive:> " + message);
                        System.Threading.Thread workThread = new System.Threading.Thread(() => ProcessCommand(message));
                        workThread.Start();

                    }
                }
            }
        }
        void readTimer_Tick(object sender, EventArgs e)
        {
            if (System.Threading.Thread.CurrentThread.Name == null)
            {
                System.Threading.Thread.CurrentThread.Name = "readTimer_Tick";
            }

            try
            {
                int bytesRead = 0;
                if (ssl)
                {
                    if (client.Available > 0)
                        bytesRead = sslstream.Read(readBuffer, 0, readBuffer.Length);
                }
                else
                {
                    if (stream.DataAvailable)
                        bytesRead = stream.Read(readBuffer, 0, readBuffer.Length);
                }
                if (bytesRead > 0)
                {
                    Read(encoding.GetString(readBuffer, 0, bytesRead));
                }
                try{
                //WriteDebug("MS = " + ms.Length);
                //WriteDebug("File Size = " + file.Size);
                }catch {}
                if (FileTransfer && file.Size == ms.Length)
                {
                    try
                    {
                        FileTransfer = false;
                        string cache = GetReadQueue();
                        if (cache.Length > 0)
                        {
                            WriteStream(encoding.GetBytes(cache));
                            //fileContent = "";
                        }
                        //string filepath = file.DownloadPath;
                        ms.Close();
                    }
                    catch { }
                    //WriteDebug("FileTransfer Complete");
                    DoFileDownload(_user, file.DownloadPath +"\\"+file.FileName);
                    try
                    {
                        //More Downloads to do?
                        if (_user.DownloadList.Count > 1)
                        {
                            _user.DownloadList.RemoveAt(0);
                            file = _user.DownloadList[0];
                            readQueue = null;
                            StartDownload();
                        }
                    }
                    catch { }
                    readTimer.Stop();
                    if (ssl)
                        sslstream.Dispose();
                    else
                        stream.Dispose();

                    ms.Dispose();
                    client.Close();
                    client = null;
                    _user = null;
                    _token = null;
                    //fileContent = null;
                    file = null;
                    GC.Collect();
                }
            }
            catch (System.IO.IOException ex)
            {
                WriteDebug("ReadTimer_Tick Error:" + ex.Message + "\n" + ex.StackTrace);
                // If we can't read from the stream, we've been disconnected
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                WriteDebug("ReadTimer_Tick Error:" + ex.Message + "\n" + ex.StackTrace);
                // Something else might be wrong perhaps?
                //Exception res = dcClient.TryReport(dcClient, ex);
                //if (res != null) ;
            }
        }
        private void WriteStream(byte[] b)
        {
            try
            {
                if (b.Length > 0)
                {
                    ms.Write(b, 0, b.Length);
                }
                ms.Flush();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                WriteDebug("WriteStream Error:" + e.Message +"\n" + e.StackTrace);
            }
        }
        private void WriteDebug(string message)
        {
            try
            {
                using (StreamWriter fs = File.AppendText(file.DownloadPath + "\\Debug.log"))
                {
                    fs.WriteLine(DateTime.Now.ToString() + " Filename: " + file.FileName + " Message: " + message);
                    fs.Flush();
                    fs.Close();
                }
            }
            catch { }
        }

        string GetReadQueue()
        {
            if (readQueue != null)
            {
                string s = readQueue.ToString();
                //WriteDebug("ReadQueue=" + s);
                readQueue = null;
                return s;
            }
            return String.Empty;
        }
        void AddToReadQueue(string overflow)
        {
            //WriteDebug("AddQueue=" + overflow);
            if (readQueue == null)
            {
                readQueue = new StringBuilder();
            }
            readQueue.Append(overflow);
        }
 
        /// <summary>
        /// Sends a raw message to the hub. 
        /// If the message is missing a linefeed (\n), it will be added to the rawMessage.
        /// </summary>
        /// <param name="message">The raw message to send.</param>
        public void SendRawMessage(string message)
        {
            try
            {
                if (!message.EndsWith("\n"))
                {
                    message += "\n";
                }
                Console.WriteLine("CtC SEND:> " + message);
                Write(message);
            }
            catch (Exception ex)
            {
                //TODO: connection broken.

            }
        }

        void Write(string message)
        {
            try
            {
                Byte[] data = encoding.GetBytes(message);

                if (ssl)
                {
                    sslstream.Write(data, 0, data.Length);
                    sslstream.Flush();
                }
                else
                {
                    stream.Write(data, 0, data.Length);
                    stream.Flush();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        private void ProcessCommand(string message)
        { 
            String command = String.Empty;
            String parameter = String.Empty;
            try
            {
                // split message into command and parameters
                command = message.Substring(0, 4);
                parameter = message.Substring(message.IndexOf(" ") + 1);
            }
            catch
            {
                command = message;
                parameter = "";
            }
            switch (command)
            { 
                case "CSUP":
                case "CSTA":
                    break;
                case "CSND":
                    FileTransfer = true;
                    break;
                case "CINF":
                    //WriteDebug("ProcessCommand CINF = " + SentCINF);
                    if (SentCINF)
                        return;
                    SentCINF = true;
                    if (_token != string.Empty)
                        SendRawMessage(String.Format("CINF ID{0} TO{1}\n", Sender.ClientInfo.CID, _token));
                    else
                        SendRawMessage(String.Format("CINF ID{0}\n", Sender.ClientInfo.CID));

                    //WriteDebug("DoCtCConnected");
                    DoCtCConnected(_user, file.DownloadPath +"\\" + file.FileName);
                    StartDownload();
                    //SendRawMessage("CGET file TTH/IPKJY6BG2KQ7AVGNTUECWHEI3IPG2WP2PPLHXEI 0 -1\n");
                    break;
                case "CGET":
                    break;
                default:
                    //fileContent += message;
                    AddToReadQueue(message);
                    break;
            }
        }

        private void StartDownload()
        {
            if (file.Filetypes.HasFlag(FileDownload.FileType.FileList))
            {
                SendRawMessage("CGET file files.xml.bz2 0 -1\n");
            }
            else if (file.Filetypes.HasFlag(FileDownload.FileType.TTH))
            {
                SendRawMessage(String.Format("CGET file TTH/{0} {1} {2}\n", file.TTH, file.Start, file.Size));
            }
            else if (file.Filetypes.HasFlag(FileDownload.FileType.File))
            {
                SendRawMessage(String.Format("CGET file {0} {1} {2}\n", file.FileName, file.Start, file.Size));
            }
            //WriteDebug("Sending CGET");
        }
        #region "FileTransfer"

        /// <summary>
        /// Occurs when a ClientToClient connection is set up.
        /// </summary>
        [Description("Occurs when a ClientToClient connection is set up.")]
        [Category("FileTransfer")]
        public event DcFileTransferHandler OnCtCConnected;
        internal void DoCtCConnected(User user, String Filename)
        {
            if (System.Threading.Thread.CurrentThread.Name == null)
                System.Threading.Thread.CurrentThread.Name = "DoCtCConnected";
            if (OnCtCConnected != null)
            {
                if (stream.DataAvailable)
                    OnCtCConnected(user,Filename);
            }
        }
        /// <summary>
        /// Occurs when a File has been sent.
        /// </summary>
        [Description("Occurs when a File has been sent.")]
        [Category("FileTransfer")]
        public event DcFileTransferHandler OnFileSend;
        internal void DoFileSend(User user, String Filename)
        {
            if (System.Threading.Thread.CurrentThread.Name == null)
                System.Threading.Thread.CurrentThread.Name = "DoFileSend";
            if (OnFileSend != null)
            {
                OnFileSend(user, Filename);
            }
        }
        /// <summary>
        /// Occurs when a File has been downloaded.
        /// </summary>
        [Description("Occurs when a File has been downloaded.")]
        [Category("FileTransfer")]
        public event DcFileTransferHandler OnFileDownload;
        internal void DoFileDownload(User user, String Filename)
        {
            if (System.Threading.Thread.CurrentThread.Name == null)
                System.Threading.Thread.CurrentThread.Name = "DoFileDownload";
            if (OnFileDownload != null)
            {
                OnFileDownload(user, Filename);
            }
        }
        #endregion
    }
}
        